#include <cstdlib>
#include <iostream>
#include <cstring>

#include "matrix.h"

using namespace std;
typedef unsigned int uint;

Matrix::Matrix(void)
    :row(0),column(0),data(0), v(0)
{
    //std::cout << __PRETTY_FUNCTION__ << std::endl;
    //allocMatrix(1,1);
}


Matrix::Matrix(const unsigned int& _row,const unsigned int& _column, const unsigned int& _type)
    : row(0),column(0),data(0), v(0)
{
    //std::cout << __PRETTY_FUNCTION__ << std::endl;

    allocMatrix( _row, _column ) ;

    // NOTA: a matriz precisava estar "zerada"
    memset( v, 0, sizeof(double)*row*column );

	switch(_type)	{
		case NORMAL_MATRIX:
			break;
		case IDENTITY_MATRIX:
			if(row != column)
				exit(1);

			for(unsigned int i = 0;i<row;i++)
				data[i][i] = 1;
			break;
	}
}

Matrix::Matrix(const Matrix& _m):row(0),column(0), data(0), v(0) {
//    std::cout << "__PRETTY_FUNCTION__ construtor" << std::endl;
    *this = _m;
}

Matrix::~Matrix() {
    delete [] v;
    v = 0;

	delete [] data;
	data = 0;

	row = column = 0;
}

unsigned int Matrix::getRow(void) const {
	return row;
}

unsigned int Matrix::getColumn() const {
	return column;
}

void Matrix::print() const {
	for(unsigned int i=0 ; i<row ; i++) {
		cout << "| ";
		for(unsigned int j=0 ; j<column ; j++)
			cout << data[i][j] << "\t\t";
		cout << " |"<< endl;
	}
}

void Matrix::print2() const {
	cout << "\n\n\n matrix(";
	for(unsigned int i = 0 ; i < row ; i++) {
		cout << "[";
		for(unsigned int j = 0 ; j < column ; j++) {
			cout << data[i][j];
			if (j != column - 1) cout << ", ";
		}
		cout << "]";
		if (i != row - 1) cout << ", ";
	}
	cout << ");\n\n\n";
}

Matrix Matrix::transpose() const {
	Matrix result(column, row);
    double** to = result.data;

	for(unsigned int i = 0; i < row; i++)
		for(unsigned int j = 0; j < column; j++)
			to[j][i] = data[i][j];
	return result;
}
double Matrix::determinant() const {
    if(row!=3 || column!=3)
    {
        cout<<"Não é uma matriz 3x3"<<endl;
        exit(1);
    }

	double diagonal1= (data[0][0] * data [1][1]) * data[2][2];
	double diagonal2 = (data[0][1] * data [1][2]) * data[2][0];
	double diagonal3 = (data[0][2] * data [1][0]) * data[2][1];
	double diagonal4= (data[2][0] * data [1][1]) * data[0][2];
	double diagonal5 = (data[2][1] * data [1][2]) * data[0][0];
	double diagonal6 = (data[2][2] * data [1][0]) * data[0][1];

	double s = (diagonal1+diagonal2)+diagonal3;
	double st = (-1.0)*((diagonal4+diagonal5)+diagonal6);
	double det = s+st;
	return det;
}
Matrix Matrix::cofactor() const {
    if(row!=3 || column!=3)
    {
        cout<<"Não é uma matriz 3x3"<<endl;
        exit(1);
    }
    Matrix result(row, column);
    double** to = result.data;

    to[0][0] = (data[1][1]*data[2][2])- (data[2][1]*data[1][2]);
    to[0][1] = (data[1][0]*data[2][2])- (data[2][0]*data[1][2]);
    to[0][2] = (data[1][0]*data[2][1])- (data[2][0]*data[2][1]);

    to[1][0] = (data[0][1]*data[2][2])- (data[2][1]*data[0][2]);
    to[1][1] = (data[0][0]*data[2][2])- (data[2][0]*data[0][2]);
    to[1][2] = (data[0][0]*data[2][1])- (data[2][0]*data[0][1]);

    to[2][0] = (data[0][1]*data[1][2])- (data[1][1]*data[0][2]);
    to[2][1] = (data[0][0]*data[1][2])- (data[1][0]*data[0][2]);
    to[2][2] = (data[0][0]*data[1][1])- (data[1][0]*data[0][1]);


	return result;


}
Matrix Matrix::inverse() const {
	Matrix result(column, row);
    double** to = result.data;
     if(row!=3 || column!=3)
    {
        cout<<"Não é uma matriz 3x3"<<endl;
        exit(1);
    }

    double det = this->determinant();

    if (det==0)
    {
        cout<<"Determinante igual a zero"<<endl;
        exit(1);

    }
    Matrix cof = this->cofactor();

	for(unsigned int i = 0; i < row; i++)
		for(unsigned int j = 0; j < column; j++)
			to[i][j] = (1.0/det)*(cof.data[i][j]);
	return result;
}


Matrix Matrix::diagonalInverse() const {
	Matrix result(row, column);
    double** to = result.data;

	for(unsigned int i = 0; i < row; i++)
	{
	    if(data[i][i]==0)
	    {
	        to[i][i]=0;
	    }
	    else
	    {
	         to[i][i] = 1.0/(data[i][i]);

	    }

	}

	return result;
}
bool Matrix::symmetricMatrix() const
{
    for (unsigned int i = 0; i<row; i++)
    {
        for (unsigned int j=0; j<column; j++)
        {
            if(data[i][j]!=data[j][i])
            {
                return false;
            }
        }
    }
    return true;
}

Vector Matrix::toVector(unsigned int _column) const {
	Vector result(row);

	for(unsigned int i = 0; i < row; i++) {
		result[i] = data[i][_column];
	}

	return result;
}

const double* Matrix::operator[](unsigned int i) const {
	return data[i];
}

double* Matrix::operator[](unsigned int i){
	return data[i];
}

Matrix Matrix::operator+(const Matrix& _matrix2) const{
	if( row == _matrix2.getRow()  && column == _matrix2.getColumn())
	{
	    const unsigned int size = row*column;

	    Matrix result(row,column);
		const double* a = v;
		const double* b = _matrix2.v;
		double *to     = result.v,
               *theEnd = result.v + size;

		do
		{
		    *to++ = (*a++) + (*b++);
		}while( to != theEnd );

		return result;
	}
	else
	{
		exit(1);
	}
}

Matrix Matrix::operator-(const Matrix & _matrix2) const {

	if( row == _matrix2.getRow()  && column == _matrix2.getColumn()) {
		Matrix result(row,column);

		const unsigned int size = row*column;
		const double* a = v;
		const double* b = _matrix2.v;
		double *to     = result.v,
               *theEnd = result.v + size;

		do
		{
		    *to++ = (*a++) - (*b++);
		}while( to != theEnd );

		return result;
	}
	else {
		exit(1);
	}
}


Matrix Matrix::operator-(void) const {
	Matrix result(row,column);

	const unsigned int size = row*column;
	const double* from = v;
    double *to     = result.v,
           *theEnd = result.v + size;

    do
	{
		    *to++ = -(*from++);
	}while( to != theEnd );

	return result;
}

Matrix Matrix::operator*( const Matrix & _matrix2) const {
	if( column != _matrix2.getRow()) {
		exit(1);
	}

	unsigned int col = _matrix2.getColumn();
	const Matrix& me = *this;
	Matrix result(row,col );
	double** to = result.data;

	for(unsigned int i = 0; i < row; i++)
	{
		for(unsigned int j = 0; j < col; j++)
		{
			double sum = 0.0;
			for(unsigned int k = 0; k < column; k++)
			{
			    sum += me[i][k] * _matrix2[k][j];
			}

			to[i][j] = sum;
		}
	}
	return result;
}

Vector Matrix::operator*(const Vector & _matrix2) const{
	if(column != _matrix2.getRow()) {
	    std::cout <<"Multiplicacao de matrizes errado!\nM1 (" << row << ", " << column  << ") * M2 (" << _matrix2.getRow() << ", 1)"<<std::endl;
		exit(1);
	}

	return ((*this)*(_matrix2).toMatrixColumn()).toVector();
}

Matrix& Matrix::operator=(const Matrix& _m) {
//    std::cout << "__PRETTY_FUNCTION__ = "<< std::endl;

    if( this == &_m )
    {
        std::cout << "illegal copy" << std::endl;
        return (*this);
    }

    if( !_m.data )
    {
        delete [] v;
        delete[] data;
        v = 0;
        data = 0;
        row = 0;
        column = 0;
    }
    else
    {
        allocMatrix( _m.row, _m.column );
        memcpy( v, _m.v, sizeof(double)*row*column );
    }
	return (*this);
}

Matrix Matrix::operator*(double _value) const {
    Matrix result(row,column);

	const unsigned int size = row*column;
	const double* from = v;
    double *to     = result.v,
           *theEnd = result.v + size;

    do
	{
		    *to++ = (*from++)*_value;
	}while( to != theEnd );

	return result;
}

void Matrix::allocMatrix(unsigned int _row, unsigned int _col){
    if( (row==_row) && (column==_col)) return;


    delete [] v;
    delete [] data;

    row = _row;
    column = _col;

    // cria uma nova row
    double* start = (v = new double[column*row]);
    data = new double*[row];

	for(unsigned int i = 0; i < row; i++)
	{
	    data[i] = start + i*column;
	}

}


